/* SexpServices.cs -- 
   Copyright (C) 2008  Casey Marshall.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.  */


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;

namespace SexpSharp.Serialization
{
	public sealed class TypeCache
	{
		private Dictionary<string, Type> typeCache;

		public TypeCache()
		{
			typeCache = new Dictionary<string, Type>();
		}

		internal Dictionary<string, Type> Cache
		{
			get {
				return typeCache;
			}
		}
	}

	public class SexpServices
	{
        public static Type LookupType(string typeName)
        {
            return LookupType(typeName, null, null, null);
        }

        public static Type LookupType(string typeName, string assemblyName)
        {
            return LookupType(typeName, assemblyName, null, null);
        }

        public static Type LookupType(string typeName, TypeCache cache)
        {
            return LookupType(typeName, null, cache, null);
        }

        

		public static Type LookupType(string typeName, string assemblyName,
									  TypeCache cache, SerializationBinder binder)
		{
            Type type = null;

            if (SexpFormatter.decTypes.TryGetValue(typeName, out type))
                return type;

			string fullName = typeName;
			if (assemblyName != null)
				fullName = fullName + "," + assemblyName;

			if (cache != null)
			{
                lock(cache)
                {
                    Dictionary<string, Type> c = cache.Cache;
                    if (c.ContainsKey(fullName))
                        return c[fullName];
                }
			}

            if (assemblyName != null && assemblyName != String.Empty)
            {
                if (binder != null)
                {
                    type = binder.BindToType(assemblyName, typeName);
                }

                Assembly assy = Assembly.Load(assemblyName);
                if (type == null)
                {
                    type = FormatterServices.GetTypeFromAssembly(assy, typeName);
                }

                if (type == null)
                {
                    Type[] types = assy.GetTypes();
                    foreach (Type t in types)
                    {
                        object[] a = t.GetCustomAttributes(typeof(SexpTypeName), true);
                        if (a.Length > 0)
                            if (((SexpTypeName) a[0]).TypeName == typeName)
                            {
                                type = t;
                                break;
                            }
                    }
                }
            }
            else
            {
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly assy in assemblies)
                {
                    type = FormatterServices.GetTypeFromAssembly(assy, typeName);

                    if (type == null)
                    {
                        Type[] types = assy.GetTypes();
                        foreach (Type t in types)
                        {
                            object[] a = t.GetCustomAttributes(typeof(SexpTypeName), true);
                            if (a.Length > 0)
                                if (((SexpTypeName) a[0]).TypeName == typeName)
                                {
                                    type = t;
                                    break;
                                }
                        }
                    }

                    if (type != null)
                        break;
                }
            }
			
			if (type != null && cache != null)
            {
                lock (cache)
                {
                    cache.Cache[fullName] = type;
                }
            }

            return type;
		}

        public static string TypeNameForType(Type t)
        {
            string name;
            SexpFormatter.InitTypeMap();
            if (SexpFormatter.encNames.TryGetValue(t, out name))
                return name;
            object[] attr = t.GetCustomAttributes(typeof(SexpTypeName), false);
            if (attr.Length > 0)
                name = ((SexpTypeName) attr[0]).TypeName;
            else
                name = t.FullName;
            return name;
        }

        public static bool TypeWithAssembly(Type type)
        {
            object[] attr;
            if ((attr = type.GetCustomAttributes(typeof(SexpWithAssembly), true)).Length > 0)
                return ((SexpWithAssembly) attr[0]).WithAssembly;
            return false;
        }
	}
}